1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
import * as React from "react"
import { notFound } from "next/navigation"
import { type SearchParams } from "@/types/table"
import { getValidFilters } from "@/lib/data-table"
import { Skeleton } from "@/components/ui/skeleton"
import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
import { Shell } from "@/components/shell"
import { InformationButton } from "@/components/information/information-button"
import {
getGtcClauses,
getUsersForFilter,
} from "@/lib/gtc-contract/gtc-clauses/service"
import { getGtcDocumentById } from "@/lib/gtc-contract/service"
import { searchParamsCache } from "@/lib/gtc-contract/gtc-clauses/validations"
import { GtcClausesPageHeader } from "@/lib/gtc-contract/gtc-clauses/gtc-clauses-page-header"
import { GtcClausesTable } from "@/lib/gtc-contract/gtc-clauses/table/clause-table"
interface GtcClausesPageProps {
params: Promise<{ id: string }>
searchParams: Promise<SearchParams>
}
export default async function GtcClausesPage(props: GtcClausesPageProps) {
const params = await props.params
const searchParams = await props.searchParams
const documentId = parseInt(params.id)
if (isNaN(documentId)) {
notFound()
}
// 문서 정보 조회
const document = await getGtcDocumentById(documentId)
if (!document) {
notFound()
}
const search = searchParamsCache.parse(searchParams)
const validFilters = getValidFilters(search.filters)
// 병렬로 데이터 조회
const promises = Promise.all([
getGtcClauses({
...search,
filters: validFilters,
documentId,
}),
getUsersForFilter()
])
return (
<Shell className="gap-2">
{/* 헤더 컴포넌트 */}
<GtcClausesPageHeader document={document} />
{/* 문서 정보 카드 */}
<div className="rounded-lg border bg-card p-4">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div>
<div className="font-medium text-muted-foreground">최초등록일</div>
<div>{document.createdAt ? new Date(document.createdAt).toLocaleDateString('ko-KR') : '-'}</div>
</div>
<div>
<div className="font-medium text-muted-foreground">최초등록자</div>
<div>{document.createdByName || '-'}</div>
</div>
<div>
<div className="font-medium text-muted-foreground">최종수정일</div>
<div>{document.updatedAt ? new Date(document.updatedAt).toLocaleDateString('ko-KR') : '-'}</div>
</div>
<div>
<div className="font-medium text-muted-foreground">최종수정자</div>
<div>{document.updatedByName || '-'}</div>
</div>
</div>
{document.editReason && (
<div className="mt-3 pt-3 border-t">
<div className="font-medium text-muted-foreground mb-1">최종 편집사유</div>
<div className="text-sm">{document.editReason}</div>
</div>
)}
</div>
{/* 조항 테이블 */}
<React.Suspense
fallback={
<DataTableSkeleton
columnCount={8}
searchableColumnCount={2}
filterableColumnCount={3}
cellWidths={["10rem", "15rem", "20rem", "30rem", "12rem", "12rem", "12rem", "8rem"]}
shrinkZero
/>
}
>
<GtcClausesTable
promises={promises}
documentId={documentId}
document={document}
/>
</React.Suspense>
</Shell>
)
}
// 메타데이터 생성
export async function generateMetadata(props: GtcClausesPageProps) {
const params = await props.params
const documentId = parseInt(params.id)
if (isNaN(documentId)) {
return {
title: "GTC 조항 관리",
}
}
try {
const document = await getGtcDocumentById(documentId)
if (!document) {
return {
title: "GTC 조항 관리",
}
}
const title = `GTC 조항 관리 - ${document.type === "standard" ? "표준" : "프로젝트"} v${document.revision}`
const description = document.project
? `${document.project.name} (${document.project.code}) 프로젝트의 GTC 조항을 관리합니다.`
: "표준 GTC 조항을 관리합니다."
return {
title,
description,
}
} catch (error) {
return {
title: "GTC 조항 관리",
}
}
}
|